package com.coccoc;

import java.util.ArrayList;
import java.util.List;

public final class Token implements Cloneable, Comparable<Token> {
    public static Token FULL_STOP = new Token(".", Type.PUNCT, SegType.END_SEG_TYPE, -1, -1);
    public static Token COMMA = new Token(",", Type.PUNCT, SegType.END_SEG_TYPE, -1, -1);
    public static Token SPACE = new Token(" ", Type.SPACE, -1, -1);

    /**
     * Compares this object with the specified object for order.  Returns a
     * negative integer, zero, or a positive integer as this object is less
     * than, equal to, or greater than the specified object.
     *
     * <p>The implementor must ensure <tt>sgn(x.compareTo(y)) ==
     * -sgn(y.compareTo(x))</tt> for all <tt>x</tt> and <tt>y</tt>.  (This
     * implies that <tt>x.compareTo(y)</tt> must throw an exception iff
     * <tt>y.compareTo(x)</tt> throws an exception.)
     *
     * <p>The implementor must also ensure that the relation is transitive:
     * <tt>(x.compareTo(y)&gt;0 &amp;&amp; y.compareTo(z)&gt;0)</tt> implies
     * <tt>x.compareTo(z)&gt;0</tt>.
     *
     * <p>Finally, the implementor must ensure that <tt>x.compareTo(y)==0</tt>
     * implies that <tt>sgn(x.compareTo(z)) == sgn(y.compareTo(z))</tt>, for
     * all <tt>z</tt>.
     *
     * <p>It is strongly recommended, but <i>not</i> strictly required that
     * <tt>(x.compareTo(y)==0) == (x.equals(y))</tt>.  Generally speaking, any
     * class that implements the <tt>Comparable</tt> interface and violates
     * this condition should clearly indicate this fact.  The recommended
     * language is "Note: this class has a natural ordering that is
     * inconsistent with equals."
     *
     * <p>In the foregoing description, the notation
     * <tt>sgn(</tt><i>expression</i><tt>)</tt> designates the mathematical
     * <i>signum</i> function, which is defined to return one of <tt>-1</tt>,
     * <tt>0</tt>, or <tt>1</tt> according to whether the value of
     * <i>expression</i> is negative, zero or positive.
     *
     * @param o the object to be compared.
     * @return a negative integer, zero, or a positive integer as this object
     * is less than, equal to, or greater than the specified object.
     * @throws NullPointerException if the specified object is null
     * @throws ClassCastException   if the specified object's type prevents it
     *                              from being compared to this object.
     */
    @Override
    public int compareTo(Token o) {
        int i = this.startPos - o.getPos();
        if (i == 0) {
            i = this.endPos - o.getEndPos();
        }
        return i;
    }

    public enum Type {
        WORD,
        NUMBER,
        SPACE,
        PUNCT,
        WHOLE_URL,
        SITE_URL;

        private static Type[] values = null;

        public static Type fromInt(int i) {
            if (Type.values == null) {
                Type.values = Type.values();
            }
            return Type.values[i];
        }
    }

    public enum SegType {
        OTHER_SEG_TYPE,
        SKIP_SEG_TYPE,
        URL_SEG_TYPE,
        END_URL_TYPE,
        END_SEG_TYPE;

        private static SegType[] values = null;

        public static SegType fromInt(int i) {
            if (SegType.values == null) {
                SegType.values = SegType.values();
            }
            return SegType.values[i];
        }
    }

    private final String text;
    private final Type type;
    private SegType segType; // Nullable
    private boolean splittedByDot;
    private final int startPos;
    private final int endPos;

    public Token(String text, int start, int end) {
        this(text, Type.WORD, start, end);
    }

    public Token(String text, Type type, int start, int end) {
        this(text, type, null, start, end);
    }

    public Token cloneWithNewText(String newText, int newEnd) {
        return new Token(newText, type, segType, splittedByDot, startPos, endPos);
    }

    public Token(String text, Type type, SegType segType, int start, int end) {
        this(text, type, segType, false, start, end);
    }

    public Token(String text, Type type, SegType segType, boolean splittedByDot, int start, int end) {
        this.text = text;
        this.type = type;
        this.segType = segType;
        this.splittedByDot = splittedByDot;
        this.startPos = start;
        this.endPos = end > 0 ? end : start + text.length();
    }

    public String getText() {
        return text;
    }

    public Type getType() {
        return type;
    }

    public int getPos() {
        return startPos;
    }

    public int getEndPos() {
        return endPos;
    }

    public SegType getSegType() {
        return segType;
    }

    public static ArrayList<String> toStringList(List<Token> tokenList) {
        ArrayList<String> temp = new ArrayList<>();
        for (Token token : tokenList) {
            temp.add(token.getText());
        }
        return temp;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(type).append(" `").append(text).append('`');
        if (segType == SegType.END_SEG_TYPE) {
            sb.append(" END");
        } else if (segType == SegType.URL_SEG_TYPE) {
            sb.append(" URL");
        } else if (segType == SegType.SKIP_SEG_TYPE) {
            sb.append(" SKIP");
        } else if (segType == SegType.END_URL_TYPE) {
            sb.append(" END_URL");
        } else {
            sb.append(" OTHER");
        }
        sb.append(' ').append(startPos).append('-').append(endPos);
        return sb.toString();
    }

    @Override
    public Token clone() {
        return new Token(text, type, segType, startPos, endPos);
    }

    @Override
    public int hashCode() {
        return text.hashCode() ^ type.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Token that = (Token) obj;

        if (!this.text.equals(that.text)) {
            return false;
        }

        if (this.type != that.type) {
            return false;
        }

        return true;
    }

    public boolean isWord() {
        return type == Type.WORD;
    }

    public boolean isPunct() {
        return type == Type.PUNCT;
    }

    public boolean isNumber() {
        return type == Type.NUMBER;
    }

    public boolean isWholeUrl() {
        return type == Type.WHOLE_URL;
    }

    public boolean isSiteUrl() {
        return type == Type.SITE_URL;
    }

    public boolean isSpace() {
        return type == Type.SPACE;
    }

    public boolean isEndSeg() {
        return segType == SegType.END_SEG_TYPE;
    }

    public boolean isSplittedByDot() {
        return splittedByDot;
    }

    public void setEndSeg() {
        segType = SegType.END_SEG_TYPE;
    }

    public void setOtherSeg() {
        segType = SegType.OTHER_SEG_TYPE;
    }

    public void setEndUrlSeg() {
        segType = SegType.END_URL_TYPE;
    }

    public void setUrlSeg() {
        segType = SegType.URL_SEG_TYPE;
    }

    public void setSkipSeg() {
        segType = SegType.SKIP_SEG_TYPE;
    }

    public boolean isUrlSeg() {
        return segType == SegType.URL_SEG_TYPE;
    }

    public boolean isEndUrlSeg() {
        return segType == SegType.END_URL_TYPE;
    }

    public boolean isSkipSeg() {
        return segType == SegType.SKIP_SEG_TYPE;
    }

    public boolean isOtherSeg() {
        return segType == SegType.OTHER_SEG_TYPE;
    }

    public boolean isWordOrNumber() {
        return isWord() || isNumber() || isSiteUrl();
    }
}
